<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Customize your own components </title>
    <script type="text/javascript" src="https://unpkg.com/dat.gui@0.7.6/build/dat.gui.min.js"></script>
    <link type="text/css" rel="stylesheet" href="https://unpkg.com/maptalks/dist/maptalks.css">
    <script type="text/javascript" src="https://unpkg.com/maptalks/dist/maptalks.js"></script>
    <script type="text/javascript" src="https://unpkg.com/three@0.138.0/build/three.min.js"></script>
    <script type="text/javascript"
        src="https://unpkg.com/three-text2d@0.5.3/dist/three-text2d.min.js"></script>

    <script type="text/javascript" src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"></script>
    <script type="text/javascript"
        src="https://unpkg.com/three@0.138.0/examples/js/libs/stats.min.js"></script>
    <script type="text/javascript" src="https://unpkg.com/rbush@3.0.1/rbush.min.js"></script>

    <style>
        html,
        body {
            margin: 0px;
            height: 100%;
            width: 100%;
        }

        #map {
            width: 100%;
            height: 100%;
            /* background-color: #000; */
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script>

        var map = new maptalks.Map("map", {
            center: [110.9912109375, 34.61853892694805],
            zoom: 5,
            minZoom: 4,

            centerCross: true,
            doubleClickZoom: false,
            baseLayer: new maptalks.TileLayer('tile', {
                urlTemplate: 'https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
                subdomains: ['a', 'b', 'c', 'd'],
                attribution: '&copy; <a href="http://osm.org">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/">CARTO</a>'
            })
        });

        maptalks.ThreeLayer.prototype.texts = [];
        maptalks.ThreeLayer.prototype.rbush = new RBush();

        maptalks.ThreeLayer.prototype.collides = function () {
            this.rbush.clear();
            for (let i = 0, len = this.texts.length; i < len; i++) {
                const text = this.texts[i];
                const textRect = text.getTextRect();
                if (this.rbush.collides(textRect)) {
                    text.hide();
                } else {
                    text.show();
                    this.rbush.insert(textRect);
                }
            }
        }


        maptalks.ThreeLayer.prototype.toText = function (coordinate, options) {
            return new TextSprite(coordinate, options, this);
        }


        // @Override
        maptalks.ThreeLayer.prototype._zoomend = function () {
            const scene = this.getScene();
            if (!scene) {
                return;
            }
            const zoom = this.getMap().getZoom();
            scene.children.forEach(mesh => {
                const parent = mesh.__parent;
                if (parent && parent.getOptions) {
                    if (!parent.getOptions().zoomFilter) {
                        return;
                    }
                    const minZoom = parent.getMinZoom(), maxZoom = parent.getMaxZoom();
                    if ((zoom < minZoom || zoom > maxZoom) && parent.isVisible()) {
                        parent.hide();
                    } else if (minZoom <= zoom && zoom <= maxZoom && (!parent.isVisible())) {
                        parent.show();
                    }
                }
            });
        }

        // @Override
        maptalks.ThreeLayer.prototype.addMesh = function (meshes, render = true) {
            if (!meshes) return this;
            if (!Array.isArray(meshes)) {
                meshes = [meshes];
            }
            const scene = this.getScene();
            meshes.forEach(mesh => {
                if (mesh instanceof maptalks.BaseObject) {
                    scene.add(mesh.getObject3d());
                    if (!mesh.isAdd) {
                        mesh.isAdd = true;
                        mesh._fire('add', { target: mesh });
                        if (mesh instanceof TextSprite) {
                            this.texts.push(mesh);
                            const textRect = mesh.getTextRect();
                            if (threeLayer.rbush.collides(textRect)) {
                                mesh.hide();
                            } else {
                                mesh.show();
                                threeLayer.rbush.insert(textRect);
                            }
                        }
                    }
                    if (mesh._animation && maptalks.Util.isFunction(mesh._animation)) {
                        this._animationBaseObjectMap[mesh.getObject3d().uuid] = mesh;
                    }
                } else if (mesh instanceof THREE.Object3D) {
                    scene.add(mesh);
                }
            });
            this._zoomend();
            // sort by weight
            this.texts.sort(function (text1, text2) {
                return text2.getOptions().weight - text1.getOptions().weight;
            });
            this.collides();
            if (render) {
                this.redraw();
            }
            return this;
        }

        /**
         * remove object3ds
         * @param {BaseObject} meshes
         */
        // @Override
        maptalks.ThreeLayer.prototype.removeMesh = function (meshes, render = true) {
            if (!meshes) return this;
            if (!Array.isArray(meshes)) {
                meshes = [meshes];
            }
            const scene = this.getScene();
            meshes.forEach(mesh => {
                if (mesh instanceof maptalks.BaseObject) {
                    scene.remove(mesh.getObject3d());
                    if (mesh.isAdd) {
                        mesh.isAdd = false;
                        mesh._fire('remove', { target: mesh });
                        if (mesh instanceof TextSprite) {
                            for (let i = 0, len = this.texts.length; i < len; i++) {
                                if (mesh === this.texts[i]) {
                                    this.texts.splice(i, 1);
                                    break;
                                }
                            }
                        }
                    }
                    if (mesh._animation && maptalks.Util.isFunction(mesh._animation)) {
                        delete this._animationBaseObjectMap[mesh.getObject3d().uuid];
                    }
                } else if (mesh instanceof THREE.Object3D) {
                    scene.remove(mesh);
                }
            });
            // sort by weight
            this.texts.sort(function (text1, text2) {
                return text2.getOptions().weight - text1.getOptions().weight;
            });
            this.collides();
            if (render) {
                this.redraw();
            }
            return this;
        }


        var threeLayer = new maptalks.ThreeLayer('t', {
            forceRenderOnMoving: true,
            forceRenderOnRotating: true,
            // animation: true
        });


        threeLayer.prepareToDraw = function (gl, scene, camera) {
            stats = new Stats();
            stats.domElement.style.zIndex = 100;
            document.getElementById('map').appendChild(stats.domElement);

            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(0, -10, 10).normalize();
            scene.add(light);
            this.redraw();

            fetch('https://webapi.amap.com/ui/1.1/ui/geo/DistrictExplorer/assets/d_v2/area_tree.json').then(res => res.json()).then(chinacitys => {
                addSprites(chinacitys);
            });

        };
        threeLayer.addTo(map);

        map.on('zooming zoomend moveend animateend', () => {
            threeLayer.collides();
        });




        var textSprites = [];
        function addSprites(chinacitys) {
            const provinceMarkers = []; //省
            const cityMarkers = []; //地市
            const districtMarkers = []; //区域

            const children = chinacitys.children;

            children.forEach(province => {
                const name = province.name;
                provinceMarkers.push({
                    name,
                    coordinates: province.center
                });

                if (!province.children) {
                    cityMarkers.push({
                        name,
                        coordinates: province.center
                    });
                }

                (province.children || []).forEach(city => {
                    const name = city.name;
                    cityMarkers.push({
                        name,
                        coordinates: city.center
                    });
                    if (city.children) {
                        city.children.forEach(district => {
                            const name = district.name;
                            districtMarkers.push({
                                name,
                                coordinates: district.center
                            });

                        });
                    }
                });
            });


            districtMarkers.forEach(element => {
                textSprites.push(threeLayer.toText(element.coordinates, { text: element.name, color: 'gray', fontSize: 14, interactive: false }));
            });

            cityMarkers.forEach(element => {
                textSprites.push(threeLayer.toText(element.coordinates, { text: element.name, color: 'black', fontSize: 16, weight: 1, interactive: false }));
            });

            provinceMarkers.forEach(element => {
                textSprites.push(threeLayer.toText(element.coordinates, { text: element.name, color: 'red', fontSize: 20, weight: 2, interactive: false }));
            });

            threeLayer.addMesh(textSprites);
            animation();
        }

        function animation() {
            // layer animation support Skipping frames
            threeLayer._needsUpdate = !threeLayer._needsUpdate;
            if (threeLayer._needsUpdate) {
                threeLayer.redraw();
                threeLayer.collides();
            }
            stats.update();
            requestAnimationFrame(animation);
        }


        //default values
        var OPTIONS1 = {
            fontSize: 20,
            altitude: 0,
            color: '#fff',
            text: 'hello',
            weight: 0,
            zoomFilter: false
        };

        /**
         * custom component
         * We can think of it as a point.
         * */

        class TextSprite extends maptalks.BaseObject {
            constructor(coordinate, options, layer) {
                options = maptalks.Util.extend({}, OPTIONS1, options, { layer, coordinate });
                super();
                //Initialize internal configuration
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
                this._initOptions(options);
                const { altitude, fontSize, color, text } = options;


                //Initialize internal object3d
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140
                this._createGroup();
                const textsprite = new THREE_Text2D.SpriteText2D(text, { align: THREE_Text2D.textAlign.center, font: `${fontSize * 2}px Arial`, fillStyle: color, antialias: false });
                textsprite.children[0].material.sizeAttenuation = false;
                const scale = 0.01 / 10 / 3;
                textsprite.scale.set(scale, scale, scale);
                this.getObject3d().add(textsprite);

                //set object3d position
                const z = layer.altitudeToVector3(altitude, altitude).x;
                const position = layer.coordinateToVector3(coordinate, z);
                this.getObject3d().position.copy(position);
                this.textRect = {
                    width: this.calTextWidth(text, fontSize),
                    height: fontSize
                }

                this._vector = new THREE.Vector3();
                this._pixel = {
                    x: 0,
                    y: 0
                };
            }

            getTextRect() {
                this.getPixel();
                const { x, y } = this._pixel;
                const { width, height } = this.textRect;
                return {
                    minX: x - width / 2,
                    minY: y - height / 2,
                    maxX: x + width / 2,
                    maxY: y + height / 2
                }
            }

            calTextWidth(text, fontSize) {
                const chinese = text.match(/[\u4e00-\u9fa5]/g) || '';
                const chineseLen = chinese.length;
                const width = chineseLen * fontSize + (text.length - chineseLen) * 0.5 * fontSize;
                return width;
            }

            getPixel() {
                const size = this.getMap().getSize();
                const camera = this.getLayer().getCamera();
                const position = this.getObject3d().position;
                this._vector.x = position.x;
                this._vector.y = position.y;
                this._vector.z = position.z;
                this._pixel = simplepath.vector2Pixel(this._vector, size, camera);
            }

            identify(coordinate) {
                const { minX, minY, maxX, maxY } = this.getTextRect();
                const pixel = this.getMap().coordToContainerPoint(coordinate);
                if (pixel.x >= minX && pixel.x <= maxX && pixel.y >= minY && pixel.y <= maxY) {
                    return true;
                }
                return false;
            }
        }


        var simplepath = {

            positionsConvert: function (worldPoints, altitude = 0, layer) {
                const vectors = [];
                for (let i = 0, len = worldPoints.length; i < len; i += 3) {
                    let x = worldPoints[i], y = worldPoints[i + 1], z = worldPoints[i + 2];
                    if (altitude > 0) {
                        z += layer.altitudeToVector3(altitude, altitude).x;
                    }
                    vectors.push(new THREE.Vector3(x, y, z));
                }
                return vectors;
            },

            vectors2Pixel: function (worldPoints, size, camera, altitude = 0, layer) {
                if (!(worldPoints[0] instanceof THREE.Vector3)) {
                    worldPoints = simplepath.positionsConvert(worldPoints, altitude, layer);
                }
                const pixels = worldPoints.map(worldPoint => {
                    return simplepath.vector2Pixel(worldPoint, size, camera);
                })
                return pixels;

            },

            vector2Pixel: function (world_vector, size, camera) {
                const vector = world_vector.project(camera);
                const halfWidth = size.width / 2;
                const halfHeight = size.height / 2;
                const result = {
                    x: Math.round(vector.x * halfWidth + halfWidth),
                    y: Math.round(-vector.y * halfHeight + halfHeight)
                };
                return result;
            },

        };





    </script>
</body>

</html>